package io.github.wslxm.springbootplus2.gateway;

import cn.hutool.core.lang.Dict;
import io.github.wslxm.springbootplus2.config.GlobalExceptionHandler;
import io.github.wslxm.springbootplus2.core.constant.ReqStateConst;
import io.github.wslxm.springbootplus2.core.result.Result;
import io.github.wslxm.springbootplus2.core.result.ResultType;
import io.github.wslxm.springbootplus2.core.utils.bean.XjSpringContextUtil;
import io.github.wslxm.springbootplus2.gateway.aopfilter.*;
import io.github.wslxm.springbootplus2.manage.sys.model.entity.SysLog;
import io.github.wslxm.springbootplus2.utils.XjErrorMsgUtil;
import io.github.wslxm.springbootplus2.utils.XjUriUtil;
import io.github.wslxm.springbootplus2.utils.model.JwtUser;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;


/**
 * Controller 请求操作/记录/权鉴
 *
 * @author wangsong
 * @version 1.0.1
 * @mail 1720696548@qq.com
 * @date 2020/11/15 0015 11:16
 */
@Slf4j
@Aspect
@Component
public class SysAspect {

    @Autowired
    private LogFilter sysLog;
    @Autowired
    private BlacklistFilter sysBlacklist;
    @Autowired
    private AuthFilter sysAuth;
    @Autowired
    private EncryptFilter sysEncrypt;
    @Autowired
    private RateLimiterFilter sysRateLimiter;

    /**
     * 全局异常
     */
    @Autowired
    private GlobalExceptionHandler globalExceptionHandler;


    /**
     * 不需要记录日志的 uri 集, 静态资源, css, js ,路由等等, 只要uri包含以下定义的内容, 将直接跳过改过滤器
     */
    private final List<String> excludeUriList = new ArrayList<>();

    public SysAspect() {
        // springbootAdmin监控相关
        excludeUriList.add("/bootAdmin/instances");
        // 系统监控相关
        excludeUriList.add("/bootAdmin");
        // 系统监控相关
        excludeUriList.add("/actuator");
        // sql监控相关
        excludeUriList.add("/druid/");
        // 页面跳转(路由)
        excludeUriList.add("/page/");
        // 模板解析错误
        excludeUriList.add("/error");
        // swagger访问
        excludeUriList.add("/swagger-resources/");
        excludeUriList.add("/api-docs");
        // 获取jvm信息
        excludeUriList.add("/api/admin/xj/jvm/jvmInfo");
        // 日志相关
        excludeUriList.add("/api/admin/adminLog/");
    }

    /**
     * 拦截范围
     *
     * @param proceed
     * @return
     * @throws Throwable
     */
    @Around("@annotation(org.springframework.web.bind.annotation.GetMapping)")
    public Object aroundGet(ProceedingJoinPoint proceed) throws Throwable {
        return run(proceed);
    }

    @Around("@annotation(org.springframework.web.bind.annotation.PostMapping)")
    public Object aroundSave(ProceedingJoinPoint proceed) throws Throwable {
        return run(proceed);
    }

    @Around("@annotation(org.springframework.web.bind.annotation.PutMapping)")
    public Object aroundUpdate(ProceedingJoinPoint proceed) throws Throwable {
        return run(proceed);
    }

    @Around("@annotation(org.springframework.web.bind.annotation.DeleteMapping)")
    public Object aroundDelete(ProceedingJoinPoint proceed) throws Throwable {
        return run(proceed);
    }

    @Around("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public Object aroundRequest(ProceedingJoinPoint proceed) throws Throwable {
        return run(proceed);
    }


    /**
     * 数据请求入口
     * <p>
     * // startTime1 = 程序开始执行时间, 由RequestFilter 过滤器中添加
     * // startTime2 = 业务代码开始执行时间
     * // endTime1   = 程序结束时间
     *
     * </P>
     *
     * @param proceed
     * @return java.lang.Object
     * @author wang-song
     * @date 2020/7/14 0014 14:14
     * @version 1.0.1
     */
    private Object run(ProceedingJoinPoint proceed) throws Throwable {
        long startTime = System.currentTimeMillis();
        // 获取请求参数
        MethodSignature signature = (MethodSignature) proceed.getSignature();
        ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (sra == null) {
            return Result.error(ResultType.SYS_ERROR_500.getValue(), "ThreadLocal Failed to get the current thread data");
        }
        HttpServletRequest request = sra.getRequest();
        HttpServletResponse response = sra.getResponse();
        String methodUri = getMethodUri(signature);
        // String requestUri = request.getRequestURI();
        String method = request.getMethod();
        // 默认请求失败
        int state = ReqStateConst.FAIL_INT;
        // 1、排除不需要处理的请求
        if (this.isExcludeUri(methodUri)) {
            // 直接执行返回
            return proceed.proceed();
        }
        // 2、请求日志
        SysLog log = sysLog.requestLogCollectAndPrint(proceed, request);

        // aop1 --> 执行前
        Result result = this.aopFilter(request, response, null, startTime, "front");
        if (!result.getCode().equals(Result.success().getCode())) {
            return result;
        }

        // 3、限流
        Result rateLimiter = sysRateLimiter.run(proceed, methodUri);
        if (!rateLimiter.getCode().equals(ResultType.SYS_SUCCESS.getValue())) {
            // 7、记录响应结果
            sysLog.responseLogAndSave(log, state, startTime, 0L, method, methodUri, rateLimiter);
            return rateLimiter;
        }

        // 4、幂等验证
        // 5、黑/白名单认证
        Result blacklistR = sysBlacklist.blacklistAuth();
        if (!blacklistR.getCode().equals(ResultType.SYS_SUCCESS.getValue())) {
            // 7、记录响应结果
            sysLog.responseLogAndSave(log, state, startTime, 0L, method, methodUri, blacklistR);
            return blacklistR;
        }

        // 6、登录授权认证
        Result<JwtUser> jwtUserR = sysAuth.loginAuth(methodUri);
        if (!jwtUserR.getCode().equals(ResultType.SYS_SUCCESS.getValue())) {
            // 7、记录响应结果
            sysLog.responseLogAndSave(log, state, startTime, 0L, method, methodUri, jwtUserR);
            return jwtUserR;
        }

        // 7、调用业务方法并记录执行时间
        Object resObj = null;
        long businessTime = 0L;

        // 6.1、请求核心参数解密
        Result<Object[]> rArgs = sysEncrypt.decrypt(proceed);

        // aop2 --> 方法执行前
        result = this.aopFilter(request, response, log, startTime, "methodFront");
        if (!result.getCode().equals(Result.success().getCode())) {
            return result;
        }

        // 6.2、请求接口
        long businessStartTime = System.currentTimeMillis();
        try {
            resObj = proceed.proceed(rArgs.getData());
            state = ReqStateConst.SUCCESS_INT;
        } catch (Exception e) {
            // 记录 业务代码异常, 这里try后, 全局异常将不生效，在直接主动调用(如果没有try exceptionHandler在异常时会自动进行拦截,在这里拦截主要是响应结果信息)
            resObj = globalExceptionHandler.exceptionHandler(e);
            businessTime = System.currentTimeMillis() - businessStartTime;
        } finally {
            // aop3 --> 方法执行后(在此处是方法异常后也能正常执行该方法)
            result = this.aopFilter(request, response, log, startTime, "methodAfter");
        }
        if (!result.getCode().equals(Result.success().getCode())) {
            return result;
        }

        // 6.3、响应核心参数自动加密
        resObj = sysEncrypt.encrypt(proceed, resObj);

        // 8、记录响应结果和记录响应时间(state=1-成功,等待请求线程执行完毕立即执行)
        sysLog.responseLogAndSave(log, state, startTime, businessTime, method, methodUri, resObj);

        // aop4 --> 执行后
        result = this.aopFilter(request, response, log, startTime, "after");
        if (!result.getCode().equals(Result.success().getCode())) {
            return result;
        }

        // 9、返回结果
        return resObj;
    }


    /**
     * 获取方法 uri
     *
     * @param signature
     * @return
     */
    private String getMethodUri(MethodSignature signature) {
        Method method = signature.getMethod();
        Class<?> clazz = method.getDeclaringClass();
        String pUri = XjUriUtil.getClassUri(clazz);
        Dict dict = XjUriUtil.getMethodUri(pUri, method);
        return dict.getStr("uri");
        //String requestMethod = dict.getStr("requestMethod");
    }

    /**
     * 是否为排除的 uri
     *
     * @param uri
     * @return
     */
    private boolean isExcludeUri(String uri) {
        for (String excludeUri : excludeUriList) {
            if (uri.contains(excludeUri)) {
                // 直接执行返回
                return true;
            }
        }
        return false;
    }


    /**
     * aop 过滤器
     *
     * @param typeName 过滤类型
     * @author 王松
     * @mail 1720696548@qq.com
     * @date 2024/9/30 13:19
     */
    private Result aopFilter(HttpServletRequest request, HttpServletResponse response, SysLog logs, long startTime, String typeName) {
        ApplicationContext context = XjSpringContextUtil.getApplicationContext();
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        List<AopExtendFilter> aopExtendFilters = new ArrayList<>();
        for (String beanName : beanDefinitionNames) {
            Object bean = context.getBean(beanName);
            if (bean instanceof AopExtendFilter) {
                aopExtendFilters.add((AopExtendFilter) bean);
            }
        }
        for (AopExtendFilter aopExtendFilter : aopExtendFilters) {
            try {
                if (typeName.equals("front")) {
                    aopExtendFilter.front(request, response);
                }
                if (typeName.equals("after")) {
                    aopExtendFilter.after(request, response);
                }
                if (typeName.equals("methodFront")) {
                    aopExtendFilter.methodFront(request, response);
                }
                if (typeName.equals("methodAfter")) {
                    aopExtendFilter.methodAfter(request, response);
                }
            } catch (Exception e) {
                Result result = Result.error(500, "aop filter " + typeName + " error " + XjErrorMsgUtil.getErrorStr(e));
                log.error("error code: {} msg: {}", result.getCode(), result.getMsg());
                if (typeName.equals("front")) {
                    // aop 前是无法记录请求日志的
                    return result;
                }
                // 过滤器中的异常
                sysLog.responseLogAndSave(logs, ReqStateConst.FAIL_INT, startTime, 0L, request.getMethod(), request.getRequestURI(), result);
                return result;
            }
        }
        return Result.success();
    }
}



